package com.wkk.websocket.config;

import com.alibaba.fastjson.JSONObject;
import com.wkk.authentication.entity.UmsUser;
import com.wkk.authentication.mapper.UmsUserMapper;
import com.wkk.websocket.constant.WebSocketConstant;
import com.wkk.exception.NoBugException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.stereotype.Component;

import javax.websocket.OnClose;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArraySet;

/**
 * @author : WangKeKe
 * @date : 2020/8/17 13:50
 * Description : webSocket服务类 用于实现消息的单发，群发 以及公告的发布
 * ---ServerEndpoint: 此注解相当于设置访问URL
 * <p>
 * <p>
 * 解决无法注入原理：
 * 本质原因：spring管理的都是单例（singleton）和 WebSocket（多对象）相冲突。
 * WebSocket 是多对象的，每个用户的聊天客户端对应 java 后台的一个 WebSocket 对象，
 * 前后台一对一（多对多）实时连接，所以 WebSocket 不可能像 servlet 一样做成单例的，
 * 让所有聊天用户连接到一个 WebSocket，这样无法保存所有用户的实时连接信息。
 * 没有让 spring 创建管理 WebSocket ，而是由 java 原来的机制管理WebSocket ，
 * 所以用户聊天时创建的 WebSocket 连接对象不是 spring 创建的，
 * spring 也不会为不是他创建的对象进行依赖注入，
 * 所以如果不用static关键字，每个 WebSocket 对象的 service 都是 null。
 * <p>
 * 初始化：项目启动时，spring 工厂会创建 WebSocket 的单例对象
 * （此时注解合法，spring 就会为 WebSocketService 类的属性 UserMapper 进行注入，
 * 并创建一个单例对象，spring 并不知道 WebSocket 的特殊意义，
 * 只是该类的注解合法，便会进行操作，与其他 controller 进行的操作一模一样），
 * 因此 UserMapper 不是 null。
 * <p>
 * 聊天时：当新用户通过客户端聊天时，后台（不管是tomcat 还是java）
 * 会根据 WebSocketService 类创建一个新的 UserMapper 对象，保存与用户的连接。
 * UserMapper 是属于类的，所以也不是 null。
 */

@Component
@Slf4j
@ServerEndpoint("/socket/{userId}")
public class WebSocketHandle {

    private Session session;

    /**
     * 解决无法注入问题
     **/
    private static UmsUserMapper userMapper;

    /**
     * 解决无法注入问题
     **/
    @Autowired
    public void setUserMapper(UmsUserMapper userMapper) {
        WebSocketHandle.userMapper = userMapper;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) {
            return true;
        }
        if (o == null || getClass() != o.getClass()) {
            return false;
        }
        WebSocketHandle that = (WebSocketHandle) o;
        return Objects.equals(session, that.session);
    }

    @Override
    public int hashCode() {
        return Objects.hash(session);
    }

    /**
     * 所有的WebSocket信息
     */
    private static CopyOnWriteArraySet<WebSocketHandle> webSockets = new CopyOnWriteArraySet<>();

    /**
     * 使用线程安全的ConcurrentHashMap
     */
    private static Map<String, Session> sessionPool = new ConcurrentHashMap<>();

    /**
     * 客户端开启socket通道时，会调用此接口
     *
     * @param session 连接信息
     * @param userId  开启通道的用户ID
     */
    @OnOpen
    public void onOpen(Session session, @PathParam(value = "userId") String userId) {
        UmsUser user = userMapper.selectById(userId);
        if (user == null) {
            throw new NoBugException("该用户不存在");
        }
        if (user.getStatus() != 1) {
            throw new NoBugException("当前用户已被冻结！");
        }

        try {
            this.session = session;
            webSockets.add(this);
            sessionPool.putIfAbsent(userId, session);
            log.info("【webSocket消息】有新的连接，总数为:" + webSockets.size());
        } catch (Exception e) {
            throw new NoBugException(e.getMessage());
        }
    }

    /**
     * 客户端关闭socket连接时 会调用此接口
     */
    @OnClose
    public void onClose() {
        try {
            webSockets.remove(this);
            log.info("【webSocket消息】连接断开，总数为:" + webSockets.size());
        } catch (Exception e) {
            throw new NoBugException(e.getMessage());
        }
    }

    /**
     * 确认WebSocket通道是开启状态
     *
     * @param message 客户端发送消息
     */
    @OnMessage
    public void onMessage(String message) {
        log.debug("【webSocket消息】收到客户端消息:" + message);
        JSONObject obj = new JSONObject();
        //业务类型
        obj.put(WebSocketConstant.MSG_CMD, WebSocketConstant.CMD_CHECK);
        //消息内容
        obj.put(WebSocketConstant.MSG_TXT, "心跳响应");
        session.getAsyncRemote().sendText(obj.toJSONString());
    }

    /**
     * 广播消息，将消息发送给所有人
     *
     * @param message 消息内容
     */
    public void sendAllMessage(String message) {
        log.info("【webSocket消息】广播消息:" + message);
        for (WebSocketHandle webSocket : webSockets) {
            try {
                if (webSocket.session.isOpen()) {
                    webSocket.session.getAsyncRemote().sendText(message);
                }
            } catch (Exception e) {
                throw new NoBugException(e.getMessage());
            }
        }
    }

    /**
     * 单点消息 - 接收方需要打开连接
     *
     * @param userId  接收消息的用户ID
     * @param message 消息
     */
    public void sendOneMessage(String userId, String message) {
        Session session = sessionPool.get(userId);
        if (session != null && session.isOpen()) {
            try {
                log.info("【webSocket消息】 单点消息:" + message);
                session.getAsyncRemote().sendText(message);
            } catch (Exception e) {
                throw new NoBugException(e.getMessage());
            }
        }
    }

    /**
     * 给多人发送消息
     *
     * @param userIds 接受消息的用户ID集合
     * @param message 消息内容
     */
    public void sendMoreMessage(String userIds, String message) {
        List<String> uidList;
        if (userIds != null && !"".equals(userIds)) {
            uidList = Arrays.asList(userIds.split(","));
            for (String userId : uidList) {
                Session session = sessionPool.get(userId);
                if (session != null && session.isOpen()) {
                    try {
                        log.info("【webSocket消息】 单点消息(多人):" + message);
                        session.getAsyncRemote().sendText(message);
                    } catch (Exception e) {
                        throw new NoBugException(e.getMessage());
                    }
                }
            }
        } else {
            throw new NoBugException("接收者ID列表-》" + null);
        }

    }
}
